/******************************************************************************* * Copyright (c) 2016 itemis AG and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Alexander Nyßen (itemis AG) - initial API & implementation * *******************************************************************************/ package org.eclipse.gef.common.tests; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedList; import org.eclipse.gef.common.beans.property.ReadOnlyListWrapperEx; import org.eclipse.gef.common.beans.property.SimpleListPropertyEx; import org.eclipse.gef.common.collections.CollectionUtils; import org.eclipse.gef.common.tests.ObservableListTests.ListChangeExpector; import org.eclipse.gef.common.tests.ObservableSetMultimapTests.InvalidationExpector; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import com.google.inject.Provider; import javafx.beans.property.ListProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @RunWith(Parameterized.class) public class ListPropertyExTests { protected static class ChangeExpector<E> implements ChangeListener<ObservableList<E>> { private ObservableValue<ObservableList<E>> source; private LinkedList<ObservableList<E>> oldValueQueue = new LinkedList<>(); private LinkedList<ObservableList<E>> newValueQueue = new LinkedList<>(); public ChangeExpector(ObservableValue<ObservableList<E>> source) { this.source = source; } public void addExpectation(ObservableList<E> oldValue, ObservableList<E> newValue) { // We check that the reference to the observable value is correct, // thus do not copy the passed in values. oldValueQueue.addFirst(oldValue); newValueQueue.addFirst(newValue); } @Override public void changed( ObservableValue<? extends ObservableList<E>> observable, ObservableList<E> oldValue, ObservableList<E> newValue) { if (oldValueQueue.size() <= 0) { fail("Received unexpected change."); } assertEquals(source, observable); assertEquals(oldValueQueue.pollLast(), oldValue); assertEquals(newValueQueue.pollLast(), newValue); } public void check() { if (oldValueQueue.size() > 0) { fail("Did not receive " + oldValueQueue.size() + " expected changes."); } } } @Parameters public static Collection<Object[]> data() { return Arrays.asList( new Object[][] { { new Provider<ListProperty<Integer>>() { @Override public ListProperty<Integer> get() { return new SimpleListPropertyEx<>(FXCollections .observableList(new ArrayList<Integer>())); } } }, { new Provider<ListProperty<Integer>>() { @Override public ListProperty<Integer> get() { // Replacement for ReadOnlySetWrapper which fixes // https://bugs.openjdk.java.net/browse/JDK-8136465) return new ReadOnlyListWrapperEx<>(FXCollections .observableList(new ArrayList<Integer>())); } } } }); } private Provider<ListProperty<Integer>> propertyProvider; public ListPropertyExTests( Provider<ListProperty<Integer>> propertyProvider) { this.propertyProvider = propertyProvider; } @Test public void bidirectionalBinding() { ListProperty<Integer> property1 = propertyProvider.get(); ListProperty<Integer> property2 = propertyProvider.get(); // XXX: According to JavaFX contract, a bidirectional binding does not // lead to the properties being regarded as bound. property2.bindBidirectional(property1); assertFalse(property1.isBound()); assertFalse(property2.isBound()); // change value of first property ObservableList<Integer> newValue = FXCollections.observableArrayList(); newValue.add(1); property1.set(newValue); assertEquals(newValue, property1.get()); assertEquals(newValue, property2.get()); assertEquals(property1, property2); // change value of second property newValue = FXCollections.observableArrayList(); newValue.add(2); property2.set(newValue); assertEquals(newValue, property1.get()); assertEquals(newValue, property2.get()); assertEquals(property1, property2); // unbind (ensure values are no longer synchronized) property2.unbindBidirectional(property1); assertFalse(property1.isBound()); assertFalse(property2.isBound()); newValue = FXCollections.observableArrayList(); newValue.add(3); property1.set(newValue); assertEquals(newValue, property1.get()); assertNotEquals(newValue, property2.get()); assertNotEquals(property1, property2); // bind on null (yields NPE) try { property2.bindBidirectional(null); fail("Expected NullPointerException because binding to null is not valid."); } catch (NullPointerException e) { // expected } // unbind from null (yields NPE) try { property2.unbindBidirectional(null); fail("Expected NullPointerException because binding to null is not valid."); } catch (NullPointerException e) { // expected } // bind on itself (yields IAE) try { property2.bindBidirectional(property2); fail("Expected IllegalArgumentException because binding to itself is not valid."); } catch (IllegalArgumentException e) { // expected } // unbind from itself (yields IAE) try { property2.unbindBidirectional(property2); fail("Expected IllegalArgumentException because binding to itself is not valid."); } catch (IllegalArgumentException e) { // expected } } @Test public void changeListenerRegistrationAndDeregistration() { ListProperty<Integer> property = propertyProvider.get(); // register listener ChangeExpector<Integer> changeListener = null; changeListener = new ChangeExpector<>(property); property.addListener(changeListener); // add second listener (and remove again) ChangeExpector<Integer> changeListener2 = null; changeListener2 = new ChangeExpector<>(property); property.addListener(changeListener2); property.removeListener(changeListener2); ObservableList<Integer> newValue = FXCollections.observableArrayList(); newValue.add(1); changeListener.addExpectation(property.get(), newValue); property.set(newValue); changeListener.check(); } /** * Check change notifications for observed value changes are properly fired. */ @Test public void changeNotifications() { ListProperty<Integer> property = propertyProvider.get(); // initialize property property.addAll(Arrays.asList(1, 2, 3)); // register listener InvalidationExpector invalidationListener = new InvalidationExpector(); ListChangeExpector<Integer> listChangeListener = new ListChangeExpector<>( property); ChangeExpector<Integer> changeListener = new ChangeExpector<>(property); property.addListener(invalidationListener); property.addListener(listChangeListener); property.addListener(changeListener); // change property value (disjoint values) ObservableList<Integer> newValue = CollectionUtils .observableList(Arrays.asList(4, 5, 6)); invalidationListener.expect(1); changeListener.addExpectation(property.get(), newValue); listChangeListener.addAtomicExpectation(); listChangeListener.addElementaryExpectation(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6), null, 0, 3); property.set(newValue); invalidationListener.check(); listChangeListener.check(); changeListener.check(); // change property value (overlapping values) newValue = CollectionUtils.observableList(Arrays.asList(5, 6, 7)); invalidationListener.expect(1); changeListener.addExpectation(property.get(), newValue); listChangeListener.addAtomicExpectation(); listChangeListener.addElementaryExpectation(Arrays.asList(4, 5, 6), Arrays.asList(5, 6, 7), null, 0, 3); property.set(newValue); invalidationListener.check(); listChangeListener.check(); changeListener.check(); // change property value (change to null) invalidationListener.expect(1); changeListener.addExpectation(property.get(), null); listChangeListener.addAtomicExpectation(); listChangeListener.addElementaryExpectation(Arrays.asList(5, 6, 7), null, null, 0, 0); property.set(null); invalidationListener.check(); listChangeListener.check(); changeListener.check(); // set to null again (no expectation) property.set(null); invalidationListener.check(); listChangeListener.check(); changeListener.check(); // change property value (change from null) newValue = CollectionUtils.observableList(Arrays.asList(1, 2, 3)); invalidationListener.expect(1); changeListener.addExpectation(null, newValue); listChangeListener.addAtomicExpectation(); listChangeListener.addElementaryExpectation(null, Arrays.asList(1, 2, 3), null, 0, 3); property.set(newValue); invalidationListener.check(); listChangeListener.check(); changeListener.check(); // set to identical value (no notifications expected) property.set(newValue); invalidationListener.check(); listChangeListener.check(); changeListener.check(); // set to equal value (no list change notification expected) newValue = CollectionUtils .observableList(new ArrayList<>(Arrays.asList(1, 2, 3))); invalidationListener.expect(1); changeListener.addExpectation(property.get(), newValue); property.set(newValue); invalidationListener.check(); listChangeListener.check(); changeListener.check(); // change observed value (only invalidation and list change expected) invalidationListener.expect(1); listChangeListener.addAtomicExpectation(); listChangeListener.addElementaryExpectation(Arrays.asList(2), null, null, 1, 1); property.get().removeAll(Arrays.asList(2)); invalidationListener.check(); listChangeListener.check(); changeListener.check(); // touch observed value (don't change it) property.get().removeAll(Arrays.asList(2)); invalidationListener.check(); listChangeListener.check(); changeListener.check(); } }